<template>
  <q-scroll-area
    ref="scroller"
    style="height: 100%; width: 100%; z-index: 0"
    @scroll="onScroll"
  >
    <div ref="container" class="container column" @mousedown="onMouseDown">
      <div ref="upper" class="col upper"></div>
      <div ref="lower" class="lower"></div>
      <div v-show="isRecording" class="recording" />
      <div ref="blocks" class="blocks">
        <AudioBlock
          :ref="setBlockRefs"
          v-for="(block, index) in audioBlocks"
          :key="index"
          v-bind="block"
          :zoom="zoom"
          :scale="scale"
          :handle="handle"
          @delete="deleteBlock(index)"
          @update="onBlockUpdate($event, index)"
          @click="select(index)"
        />
      </div>
    </div>
  </q-scroll-area>
</template>
<script setup lang="ts">
import {
  ref,
  onMounted,
  watch,
  computed,
  onBeforeUpdate,
  createBlock,
} from 'vue';
import { QScrollArea } from 'quasar';
import AudioBlockInfo from './blocks/AudioBlockInfo';
import AudioBlock from './blocks/AudioBlock.vue';
import { Channel, nidx } from 'src/utils/AudioMixer';
import useUndoRedo from 'src/mixins/UndoRedo';
import StudioOperate, { OperateType } from 'src/models/StudioOperate';
import { useVideoStore } from 'src/stores/video-store';
const videoStore = useVideoStore();
const isPlaying = computed<boolean>(() => videoStore.isPlaying);
const currentTime = computed<number>(() => videoStore.currentTime);
const props = defineProps({
  duration: {
    type: Number,
    default: 0,
  },
  zoom: {
    type: Number,
    default: 1,
  },
  scroll: {
    type: Boolean,
    default: false,
  },
  scrollPosition: {
    type: Number,
    default: 0,
  },
  handle: {
    type: Boolean,
    default: false,
  },
  isRecording: {
    type: Boolean,
    default: false,
  },
});
const emit = defineEmits(['scroll', 'select']);
const container = ref<HTMLDivElement>();
const scroller = ref<QScrollArea>();
const blocks = ref<HTMLDivElement>();
const { addAction } = useUndoRedo();
let blockRefs: any[] = [];
let scale = ref(0);
let clientWidth = 0;
let clientHeight = 0;
let width = ref(0);
let channel: Channel | null = null;
let audioBuffer: AudioBuffer | null = null;
watch(
  () => props.duration,
  () => {
    update();
  }
);
watch(
  () => props.zoom,
  () => {
    update();
  }
);
watch(
  () => props.scrollPosition,
  () => {
    if (scroller.value) {
      scroller.value?.setScrollPosition('horizontal', props.scrollPosition);
    }
  }
);
onMounted(() => {
  clientWidth = container.value!.parentElement!.clientWidth;
  clientHeight = container.value!.clientHeight;
  update();
});
function setBlockRefs(el: any) {
  if (el) {
    if (blockRefs.indexOf(el) === -1) {
      blockRefs.push(el);
    }
  }
}
function update() {
  clientWidth = container.value!.parentElement!.clientWidth;
  clientHeight = container.value!.clientHeight;
  scale.value = clientWidth / props.duration;
  width.value = scale.value * props.duration * props.zoom;
  if (container.value) {
    container.value.style.width = `${width.value}px`;
  }
}
function onScroll({ horizontalPosition }: any) {
  emit('scroll', horizontalPosition);
}
let pos = { x: 0, left: 0 };
function onMouseDown(event: MouseEvent) {
  deselelct();
  if (!props.scroll) {
    return;
  }
  pos.x = event.clientX;
  const left = scroller.value?.getScrollPosition().left;
  if (left) {
    pos.left = left;
  }
  window.addEventListener('mousemove', onMouseMove);
  window.addEventListener('mouseup', onMouseUp);
}
function onMouseUp(event: MouseEvent) {
  window.removeEventListener('mousemove', onMouseMove);
  window.removeEventListener('mouseup', onMouseUp);
  pos = { x: 0, left: 0 };
}
function onMouseMove(event: MouseEvent) {
  const dx = event.clientX - pos.x;
  let offset = pos.left - dx;
  if (offset < 0) {
    offset = 0;
  }
  scroller.value?.setScrollPosition('horizontal', offset);
}
const audioBlocks = ref<
  {
    start: number;
    name: string;
    audioBuffer: AudioBuffer;
    blob: Blob;
    info: AudioBlockInfo | null;
  }[]
>([]);
function initChannel(chn: Channel) {
  channel = chn;
}
function addAudioBlock(
  buffer: AudioBuffer,
  start = 0,
  blob: Blob,
  name = '',
  operateFlag = true
) {
  if (!audioBuffer && channel) {
    audioBuffer = channel.createBuffer(props.duration, buffer.sampleRate);
  }
  const voice = {
    start: start,
    name: name === '' ? `record${audioBlocks.value.length + 1}` : name,
    audioBuffer: buffer,
    blob: blob,
    info: null,
  };
  audioBlocks.value.push(voice);
  clacAudios();
  if (operateFlag) {
    const operate = new StudioOperate();
    operate.type = OperateType.AddVoice;
    operate.action = 'addAudioBlock';
    operate.undo = removeVoice;
    operate.undoParams = [voice];
    operate.redo = addVoice;
    operate.redoParams = [voice];
    addAction(operate);
  }
  return voice;
}
function addVoice(voice: any) {
  audioBlocks.value.push(voice);
  clacAudios();
}
function removeVoice(voice: any) {
  const index = audioBlocks.value.indexOf(voice);
  if (index !== -1) {
    audioBlocks.value.splice(index, 1);
    clacAudios();
  }
}
function addAudioBlock2(blob: Blob, start = 0, name = '') {
  const reader = new FileReader();
  reader.onload = (e: any) => {
    channel
      ?.getContext()
      .decodeAudioData(reader.result as ArrayBuffer, (buffer: AudioBuffer) => {
        addAudioBlock(buffer, start, blob, name);
      });
  };
  reader.readAsArrayBuffer(blob);
}
function onBlockUpdate(block: AudioBlockInfo, index: number) {
  audioBlocks.value[index].info = block;
  clacAudios();
}
function deleteBlock(index: number) {
  audioBlocks.value.splice(index, 1);
  clacAudios();
}
function clacAudios() {
  clearAudioBuffer();
  if (blockRefs.length > 0 && audioBuffer) {
    const target = audioBuffer.getChannelData(0);
    blockRefs.forEach((block) => {
      const info: AudioBlockInfo = block.getCurrentInfo();
      let data = info.audioBuffer!.getChannelData(0);
      let startIndex = Math.floor(
        info.paddingLeft * info.audioBuffer!.sampleRate
      );
      if (startIndex < 0) {
        startIndex = 0;
      }
      let endIndex = Math.floor(
        (info.audioBuffer!.duration - info.paddingRight) *
          info.audioBuffer!.sampleRate
      );
      data = data.slice(startIndex, endIndex);
      const start = Math.round(info.start * info.audioBuffer!.sampleRate);
      for (let i = 0; i < data.length; i++) {
        target[start + i] = data[i];
      }
    });
  }
}
function clearAudioBuffer() {
  if (audioBuffer) {
    const data = audioBuffer.getChannelData(0);
    data.fill(0, 0, data.length);
  }
}
watch(isPlaying, (value) => {
  if (value) {
    if (channel && audioBuffer) {
      channel.play(currentTime.value);
    }
  } else {
    if (channel && audioBuffer) {
      channel.stop();
    }
  }
});
function getBlockListInfo() {
  if (blockRefs.length > 0) {
    let infoArr: AudioBlockInfo[] = [];
    blockRefs.forEach((instance) => {
      const info = instance.getCurrentInfo();
      info.audioBuffer = null;
      infoArr.push(info);
    });
    return infoArr;
  } else {
    return [];
  }
}
function restore(list: AudioBlockInfo[]) {
  if (list.length > 0) {
    list.forEach((info) => {
      addAudioBlock2(info.blob!, info.start, info.name);
    });
    setTimeout(() => {
      clacAudios();
    }, 1000);
  }
}
function select(index: number) {
  if (!props.handle) {
    return;
  }
  if (blockRefs.length > 0) {
    blockRefs.forEach((block) => {
      block.unselect();
    });
    blockRefs[index].select();
    emit('select', index);
  }
}
function deselelct() {
  if (!props.handle) {
    return;
  }
  if (blockRefs.length > 0) {
    blockRefs.forEach((block) => {
      block.unselect();
    });
  }
}
function createBlockByInfo(info: any) {
  const voice = {
    start: info.start,
    name: info.name,
    audioBuffer: info.audioBuffer,
    blob: info.blob,
    info: info,
  };
  audioBlocks.value.push(voice);
}
function split(index: number) {
  const selectedBlock = blockRefs[index];
  let info1 = selectedBlock.getCurrentInfo();
  let info2 = selectedBlock.getCurrentInfo();
  info1.paddingRight = info1.start + info1.duration - currentTime.value;
  console.log(`info1.paddingRight:${info1.paddingRight}`);
  selectedBlock.setInfo(info1);
  info2.paddingLeft = currentTime.value - info2.start;
  info2.name = `${info2.name}_split`;
  createBlockByInfo(info2);
}
function clearAll() {
  audioBlocks.value = [];
  clearAudioBuffer();
  blockRefs = [];
  clacAudios();
}
defineExpose({
  initChannel,
  addAudioBlock,
  addAudioBlock2,
  clacAudios,
  getBlockListInfo,
  restore,
  deselelct,
  split,
  clearAll,
});
</script>
<style lang="sass" scoped>

.container
  z-index: 0
  position: absolute
  height: 100%
  overflow: hidden
  .recording
    width: 100%
    height: 100%
    position: absolute
    background-color: rgba(299,55,72,0.1)
  .blocks
    height: 100%
    position: absolute
    top: 0
    left: 0
  .lower
    height: 25px
    background-color: #161616
    z-index: 0
</style>
